<?php

declare(strict_types=1);

namespace MultiWorker;

use MultiWorker\Exception\PipeException;

class Pipeline
{   
    /**
     * 名称
     *
     * @var string
     */
    protected $name;

    /**
     * 存储目录
     *
     * @var string
     */
    protected $dir;

    /**
     * 存储路径
     *
     * @var string
     */
    protected $path;

    /**
     * 命名前缀
     *
     * @var string
     */
    protected $prefix = 'multi-worker-pipe.';

    /**
     * size
     *
     * @var integer
     */
    protected $size = 1024;

    /**
     * sleep time microtime /μs
     *
     * @var integer
     */
    protected $loopMicrotime = 200000;

    /**
     * pipe file mode.
     *
     * @var integer
     */
    protected $mode = 0666;

    /**
     * Pipeline Constructor.
     *
     * @param string $name
     * @param string|null $dir
     */
    public function __construct(string $name,?string $dir = null,?string $prefix = null)
    {
        $this->name   = $name;
        $this->prefix = $prefix ?: 'multi-worker-pipe.';
        $this->dir    = $dir ?: sys_get_temp_dir();
        is_dir($this->dir) || mkdir($this->dir,0755,true);
        $this->path   = $this->dir . DIRECTORY_SEPARATOR . $this->getPipeName();

        clearstatcache();
        if (!file_exists($this->path)) {
            $this->makePipe();
        }
    }

    /**
     * make pipe
     *
     * @return void
     */
    public function makePipe ()
    {
        if (!posix_mkfifo($this->path,$this->mode)) {
            throw new PipeException("{$this->path} pipe make error.");
        }
    }

    /**
     * 获取管道文件名称
     *
     * @return string
     */
    public function getPipename (): string
    {
        return $this->prefix . $this->name;
    }

    /**
     * 写管道
     *
     * @param mixed $signal
     * @return bool
     */
    public function write ($signal): bool
    {
        $pipe = fopen($this->path,'w');
        if (!$pipe) {
            throw new PipeException("管道文件打开失败." . $this->name);
        }
        if (!fwrite($pipe,(string)$signal)) {
            throw new PipeException("管道文件写入失败." . $this->name);  
        }
        if (!fclose($pipe)) {
            throw new PipeException("管道文件关闭失败." . $this->name); 
        }
        return true;
    }

    /**
     * 读管道
     *
     * @return mixed
     */
    public function read ()
    {
        while (!file_exists($this->path)) {
            usleep($this->loopMicrotime);
        }
        do {
            $pipe = fopen($this->path,'r+');
            usleep($this->loopMicrotime);
        } while(!$pipe);

        stream_set_blocking($pipe, false);

        $data = fread($pipe,$this->size);
        if (!fclose($pipe)) {
            throw new PipeException("管道文件关闭失败." . $this->name); 
        }
        return $data;
    }

    /**
     * 清除管道
     *
     * @return boolean
     */
    public function clear (): bool
    {
        clearstatcache();
        if (file_exists($this->path) && !unlink($this->path)) {
            throw new PipeException("管道文件清除失败");
        }
        return true;
    }
}